/* 
 *  Copyright 2012 CodeMagi, Inc.
 * 
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
package com.codemagi.servlets.content.model;

import com.codemagi.login.model.IUser;
import com.codemagi.servlets.model.Node;
import com.codemagi.servlets.model.Searchable;
import com.codemagi.servlets.model.Securable;
import com.codemagi.util.*;
import java.io.Serializable;
import java.util.*;
import org.apache.log4j.Logger;

/**
 * Models a NavPage, with full contents loaded from persistence.
 *
 * @version 1.0
 * @author August Detlefsen for CodeMagi, Inc.
 */
public class NavPageContent extends com.codemagi.servlets.model.Node implements Serializable, Comparable, Securable, Searchable {

    Logger log = Logger.getLogger(this.getClass());

    //constants
    public static final Integer ACCESS_READ_ONLY    = new Integer(1);
    public static final Integer ACCESS_EDIT         = new Integer(2);
    public static final Integer ACCESS_DELETE       = new Integer(3);

    //members                                                                                                 
    private String title;
    private String keywords;
    private String link;
    private ISection section;
    private Integer sortOrder;
    private Boolean loginRequired     = new Boolean(false);
    private Boolean restrictToSection = new Boolean(false);
    private Boolean display           = new Boolean(true);
    private Boolean displayHome       = new Boolean(true);
    private Date expireDate       = null;
    private String content;
    private Date contentDate;
    private NavTemplate template;
    private ArrayList<VanityUrl> vanityUrls  = new ArrayList();


    //GETTERS AND SETTERS ------------------------------------          

    public void setTitle(String newValue) {
        title = newValue;
    }

    public String getTitle() {
        return title;
    }

    public void setKeywords(String newValue) {
        keywords = newValue;
    }

    public String getKeywords() {
        return keywords;
    }

    public void setLink(String newValue) {
        link = newValue;
    }

    public String getLink() {
        return link;
    }

    public void setSection(ISection newValue) {
        section = newValue;
    }

    public ISection getSection() {
        return section;
    }

    public void setSortOrder(Integer newValue) {
        sortOrder = newValue;
    }

    public Integer getSortOrder() {
        return sortOrder;
    }

    public void setLoginRequired(Boolean newValue) {
        if (newValue != null) loginRequired = newValue;
    }

    public Boolean getLoginRequired() {
        return loginRequired;
    }

    public void setRestrictToSection(Boolean newValue) {
        if (newValue != null) restrictToSection = newValue;
    }

    public Boolean getRestrictToSection() {
        return restrictToSection;
    }


    public void setDisplay(Boolean newValue) {
        if (newValue != null) display = newValue;
    }

    public Boolean getDisplay() {
        return display;
    }


    public void setDisplayHome(Boolean newValue) {
        if (newValue != null) displayHome = newValue;
    }

    public Boolean getDisplayHome() {
        return displayHome;
    }


    public void setExpireDate(Date newValue) {
	expireDate = newValue;
    }

    public Date getExpireDate() {
        return expireDate;
    }


    /**
     * @deprecated   Replaced by Node.setCreatedDate()
     */
    public void setDateCreated(Date newValue) {
	setCreatedDate(newValue);
    }

    /**
     * @deprecated   Replaced by Node.getCreatedDate()
     */
    public Date getDateCreated() {
        return getCreatedDate();
    }


    public void setContent(String newValue) {
        content = newValue;
    }

    public String getContent() {
        return content;
    }

    public void setContentDate(Date newValue) {
        contentDate = newValue;
    }

    public Date getContentDate() {
        return contentDate;
    }

    public void setTemplate(NavTemplate newValue) {
        template = newValue;
    }

    public NavTemplate getTemplate() {
        return template;
    }

    public void setVanityUrls(ArrayList newValue) {
        if (newValue != null) vanityUrls = newValue;
    }

    public ArrayList getVanityUrls() {
        return vanityUrls;
    }


    //SPECIAL GETTERS AND SETTERS ------------------------------------
    
    public String getPublicLink() {
	if (!vanityUrls.isEmpty()) {
	    VanityUrl vu = (VanityUrl)vanityUrls.get(0);
	    return vu.getVanityUrl();
	}

	return getLink();
    }

    public Integer getTemplateId() {
	if (template == null) return null;

	return template.getId();
    }

    public String getTemplatePath() {
	if (template == null) return "";
	
	return template.getPath();
    }

    public void addVanityUrl(VanityUrl newValue) {
	if (!vanityUrls.contains(newValue)) vanityUrls.add(newValue);
    }

    public Integer getSectionId() {
	return section == null ? new Integer(0) : section.getId();
    }

    public String getSectionName() {
	if (section == null) return "";

	return section.getName();
    }

    public boolean hasChildren() {
        return (!getChildren(this.getClass()).isEmpty());
    }

    /**
     * Returns true if this nav page has at least one visible child
     */
    public boolean hasVisibleChildren() {
        if ( children.isEmpty() ) return false;

	Iterator i     = getChildren(com.codemagi.servlets.content.model.NavPageContent.class).iterator();
	while (i.hasNext()) {
	    NavPageContent child = (NavPageContent)i.next();

	    if (child.display()) return true;
	}

	return false;
    }

    /**
     * Returns true if this nav page has at least one visible 
     * child that the passed user is allowed to view
     */
    public boolean hasVisibleChildren(IUser user) {
        if ( children.isEmpty() ) return false;

	Iterator i     = getChildren(com.codemagi.servlets.content.model.NavPageContent.class).iterator();
	while (i.hasNext()) {
	    NavPageContent child = (NavPageContent)i.next();

	    if (!user.isAuthenticated() && child.isLoginRequired()) continue;

	    if (child.display()) return true;
	}

	return false;
    }

    public List<NavPageContent> getSiblings() {
	Node parent = getParent();

        if (parent == null || !(parent instanceof com.codemagi.servlets.content.model.NavPageContent)) return new ArrayList();

        return parent.getChildren(com.codemagi.servlets.content.model.NavPageContent.class);
    }

    public boolean isRootPage() {
	return (getParent() == null);
    }

    public boolean isLoginRequired() {
        return loginRequired.booleanValue();
    }

    public boolean isRestrictToSection() {
        return restrictToSection.booleanValue();
    }

    public boolean display() {
        return display.booleanValue();
    }

    public boolean displayHome() {
        return displayHome.booleanValue();
    }

    public String getPageTitle() {
        return Utils.isEmpty(title) ? getName() : title ;
    }

    public String getParentPageTitle() {
	Node parent = getParent();

        if (parent == null || !(parent instanceof NavPageContent)) return "";

        return parent.getName();
    }

    public Integer getParentPageId() {
	Node parent = getParent();

        if (parent == null) return null;

        return parent.getId();
    }

    public LinkedHashMap getBreadcrumbs() {
        return getBreadcrumbs(this, new LinkedHashMap());
    }


    private LinkedHashMap getBreadcrumbs(NavPageContent page, LinkedHashMap breadcrumbs) {

        if (page == null || page.getId() == null) return breadcrumbs;

        breadcrumbs = CollectionUtils.putAt(breadcrumbs, page.getPublicLink(), page.getName(), 0);

        Node parentPage = page.getParent();
        if (parentPage != null && parentPage.getId() != null && parentPage instanceof NavPageContent) {
            return getBreadcrumbs( (NavPageContent)parentPage, breadcrumbs);
        }

        return breadcrumbs;
    }

    Integer levelCache = null;

    /**
     * Returns this NavPage's level in the page hierarchy
     */
    public int getLevel() {
	
	if (levelCache != null) return levelCache;

	if ( isRootPage() ) return 0;

	int level = 1;
	Node testParent = getParent();
	while ( testParent != null && testParent instanceof NavPageContent && !((NavPageContent)testParent).isRootPage() ) {
	    level++;
	    testParent = testParent.getParent();
	}

	//cache it
	levelCache = level;

	return level;
    }

    /**
     * Returns the ancestry of this page, in <i>descending</i> order. 
     *
     * That is, the ROOT page will be first, followed by the section, followed by subpages, etc.
     */
    public List<Node> getAncestry() {

	List<Node> output = new ArrayList<Node>();

	Node testParent = getParent();
        while ( testParent != null ) {
	    output.add(0, testParent);

            testParent = testParent.getParent();
        }

	log.debug("Ancestry: " + output);
        return output;
    }

    /**
     * Returns the parent of this NavPageContent that is at a certain level in the page hierarchy
     */
    public Node getAncestor(int level) {
	List<Node> ancestry = getAncestry();

	//avoid AIOOBE
	if (level >= ancestry.size()) return null;

	return ancestry.get(level);
    }

    /**
     * Returns true if this NavPage has the passed NavPage as an ancestor
     */
    public boolean hasAncestor(NavPageContent input) {

	//sanity check
	if (input == null) return false;

        Node testParent = getParent();
        while ( testParent != null ) {
	    if (testParent.getId().equals(input.getId()) ) return true;
	    
            testParent = testParent.getParent();
        }

	return false;
    }


    /**
     * Returns true if this NavPage has the NavPage represented by the passed ID as an ancestor
     */
    public boolean hasAncestor(Integer input) {
        Node testParent = getParent();
        while ( testParent != null ) {
	    if (testParent.getId() != null && testParent.getId().equals(input) ) return true;
	    
            testParent = testParent.getParent();
        }

	return false;
    }


    /**
     * Returns true if the user has view-level access to this page
     */
    public boolean hasUser(IUser user) {
	if (user != null && user.isSuperuser()) return true;

	if (isRestrictToSection()) return section != null && section.hasUser(user);

	return !isLoginRequired() || (user != null && user.isAuthenticated()) ;
    }


    public boolean hasUser(IUser user, Integer accessLevel) {
	//forward read requests to the one arg method
	if (accessLevel == null || ACCESS_READ_ONLY.equals(accessLevel)) return hasUser(user);

	//edit and delete access are the same in this implementation
	return user.isSuperuser() || (section != null && section.hasUser(user));
    }


    public boolean isExpired() {
	return (expireDate != null && expireDate.before(new Date()) );
    }


    //UTILITY METHODS ---------------------------------------------                                              

    /**
     * Implementation of Comparable interface. Pages are sorted by sortOrder and then alphabetically
     */
    public int compareTo(Object o2) {
	
	int output = 0;
	
	try {
	    NavPageContent n2 = (NavPageContent)o2;
	    
	    if ( this.getSortOrder() != null && n2.getSortOrder() != null ) {
		output = this.getSortOrder().compareTo( n2.getSortOrder() );
		
	    } else if ( n2.getSortOrder() == null && this.getSortOrder() != null ) {
		output = -1;
		
	    } else if ( this.getSortOrder() == null && n2.getSortOrder() != null ) {
		output =  1;
		
	    } else {
		output = this.getName().compareTo( n2.getName() );
		
	    }
	    
	} catch (Exception e) {
	    //NullPointerException, ClassCastException, for example                             
	}
	
	return output;
    }

    /**
     * Implementation of the Searchable interface.
     */
    public String getSearchText() {
        StringBuffer output = new StringBuffer(super.getSearchText());
	output.append(" ");

        output.append( StringUtils.stripHtmlRegex( title ) ).append(" ");
        output.append( StringUtils.stripHtmlRegex( content ) ).append(" ");
        output.append( StringUtils.stripHtmlRegex( keywords ) );

	for (VanityUrl url : vanityUrls) {
	    output.append(" ").append( StringUtils.replace(url.getVanityUrl(), "/", " ") );
	}

        return StringUtils.stripPunctuation( output.toString() );
    }

}
